/*-------------------------------------------------------------------------
 *
 * bootstrap.c
 *	  支持以“引导”模式运行 postgres 的例程
 *	  引导模式用于创建初始模板数据库
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/bootstrap/bootstrap.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <unistd.h>
#include <signal.h>

#include "access/genam.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/tableam.h"
#include "access/toast_compression.h"
#include "access/xact.h"
#include "access/xlog_internal.h"
#include "bootstrap/bootstrap.h"
#include "catalog/index.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "common/link-canary.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pg_getopt.h"
#include "storage/bufmgr.h"
#include "storage/bufpage.h"
#include "storage/condition_variable.h"
#include "storage/ipc.h"
#include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/relmapper.h"
#ifdef FDD
#include "utils/ps_status.h"
extern char  *g_tdepass ;
#endif

uint32		bootstrap_data_checksum_version = 0;	/* 无校验和 */


static void fc_CheckerModeMain(void);
static void fc_bootstrap_signals(void);
static Form_pg_attribute fc_AllocateAttribute(void);
static void fc_populate_typ_list(void);
static Oid	fc_gettype(char *fc_type);
static void fc_cleanup(void);

/* ----------------
 *		全局变量
 * ----------------
 */

Relation	boot_reldesc;		/* 当前关系描述符 */

Form_pg_attribute attrtypes[MAXATTR];	/* 指向属性信息 */
int			numattr;			/* 当前关系的属性数量 */


/*
 * 与每个类型关联的基本信息。这在 pg_type 被填充之前使用，
 * 所以它必须涵盖作为列类型在核心“引导”目录中使用的数据类型。
 *
 *		XXX 其中几个输入/输出函数进行目录扫描
 *			（例如，F_REGPROCIN 扫描 pg_proc）。这明显在目录创建过程
 *			中造成了一些顺序依赖关系。
 */
struct typinfo
{
	char		name[NAMEDATALEN];
	Oid			oid;
	Oid			elem;
	int16		len;
	bool		byval;
	char		align;
	char		storage;
	Oid			collation;
	Oid			inproc;
	Oid			outproc;
};

static const struct typinfo TypInfo[] = {
	{"bool", BOOLOID, 0, 1, true, TYPALIGN_CHAR, TYPSTORAGE_PLAIN, InvalidOid,
	F_BOOLIN, F_BOOLOUT},
	{"bytea", BYTEAOID, 0, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, InvalidOid,
	F_BYTEAIN, F_BYTEAOUT},
	{"char", CHAROID, 0, 1, true, TYPALIGN_CHAR, TYPSTORAGE_PLAIN, InvalidOid,
	F_CHARIN, F_CHAROUT},
	{"int2", INT2OID, 0, 2, true, TYPALIGN_SHORT, TYPSTORAGE_PLAIN, InvalidOid,
	F_INT2IN, F_INT2OUT},
	{"int4", INT4OID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
	F_INT4IN, F_INT4OUT},
	{"float4", FLOAT4OID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
	F_FLOAT4IN, F_FLOAT4OUT},
	{"name", NAMEOID, CHAROID, NAMEDATALEN, false, TYPALIGN_CHAR, TYPSTORAGE_PLAIN, C_COLLATION_OID,
	F_NAMEIN, F_NAMEOUT},
	{"regclass", REGCLASSOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
	F_REGCLASSIN, F_REGCLASSOUT},
	{"regproc", REGPROCOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
	F_REGPROCIN, F_REGPROCOUT},
	{"regtype", REGTYPEOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
	F_REGTYPEIN, F_REGTYPEOUT},
	{"regrole", REGROLEOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
	F_REGROLEIN, F_REGROLEOUT},
	{"regnamespace", REGNAMESPACEOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
	F_REGNAMESPACEIN, F_REGNAMESPACEOUT},
	{"text", TEXTOID, 0, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, DEFAULT_COLLATION_OID,
	F_TEXTIN, F_TEXTOUT},
	{"oid", OIDOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
	F_OIDIN, F_OIDOUT},
	{"tid", TIDOID, 0, 6, false, TYPALIGN_SHORT, TYPSTORAGE_PLAIN, InvalidOid,
	F_TIDIN, F_TIDOUT},
	{"xid", XIDOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
	F_XIDIN, F_XIDOUT},
	{"cid", CIDOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
	F_CIDIN, F_CIDOUT},
	{"pg_node_tree", PG_NODE_TREEOID, 0, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, DEFAULT_COLLATION_OID,
	F_PG_NODE_TREE_IN, F_PG_NODE_TREE_OUT},
	{"int2vector", INT2VECTOROID, INT2OID, -1, false, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
	F_INT2VECTORIN, F_INT2VECTOROUT},
	{"oidvector", OIDVECTOROID, OIDOID, -1, false, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
	F_OIDVECTORIN, F_OIDVECTOROUT},
	{"_int4", INT4ARRAYOID, INT4OID, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, InvalidOid,
	F_ARRAY_IN, F_ARRAY_OUT},
	{"_text", 1009, TEXTOID, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, DEFAULT_COLLATION_OID,
	F_ARRAY_IN, F_ARRAY_OUT},
	{"_oid", 1028, OIDOID, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, InvalidOid,
	F_ARRAY_IN, F_ARRAY_OUT},
	{"_char", 1002, CHAROID, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, InvalidOid,
	F_ARRAY_IN, F_ARRAY_OUT},
	{"_aclitem", 1034, ACLITEMOID, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, InvalidOid,
	F_ARRAY_IN, F_ARRAY_OUT}
};

static const int n_types = sizeof(TypInfo) / sizeof(struct typinfo);

struct typmap
{								/* a hack */
	Oid			am_oid;
	FormData_pg_type am_typ;
};

static List *Typ = NIL;			
/* 结构typmap的列表 */
static struct typmap *Ap = NULL;

static Datum values[MAXATTR];	/* 当前行的属性值 */
static bool Nulls[MAXATTR];

static MemoryContext nogc = NULL;	/* 特殊的无垃圾回收内存上下文 */

/*
 *	在启动时，我们首先声明所有要构建的索引，然后构建它们。
 *	IndexList 结构存储足够的信息，以便在索引声明后构建它们。
 */

typedef struct _IndexList
{
	Oid			il_heap;
	Oid			il_ind;
	IndexInfo  *il_info;
	struct _IndexList *il_next;
} IndexList;

static IndexList *ILHead = NULL;


/*
 * 在共享内存检查器模式下，我们真正想做的只是创建共享内存和信号量
 * （只是为了证明我们可以使用当前的 GUC 设置做到这一点）。
 * 事实上，这已经通过 CreateSharedMemoryAndSemaphores() 完成了，
 * 所以我们在这里没有更多的事情要做。
 */
static void fc_CheckerModeMain(void)
{
	proc_exit(0);
}

/*
 *	 以引导模式运行后端的主要入口点
 *
 *	 引导模式用于初始化模板数据库。
 *	 引导后端不使用 SQL，而是期望使用一种特殊的引导语言。
 *
 *	 当 check_only 为 true 时，启动仅进行到足够验证当前配置的程度，
 *	 特别是与共享内存大小相关的传入选项是否有效（或至少不会在共享
 *	 内存创建之前导致错误）。
 */
void BootstrapModeMain(int fc_argc, char *fc_argv[], bool fc_check_only)
{
	int			fc_i;
	char	   *fc_progname = fc_argv[0];
	int			fc_flag;
	char	   *fc_userDoption = NULL;

	Assert(!IsUnderPostmaster);

	InitStandaloneProcess(fc_argv[0]);

	/* 设置默认值，将被下面的显式选项覆盖 */
	InitializeGUCOptions();

	/* 应该存在初始 --boot 或 --check */
	Assert(fc_argc > 1
		   && (strcmp(fc_argv[1], "--boot") == 0
			   || strcmp(fc_argv[1], "--check") == 0));
	fc_argv++;
	fc_argc--;

#ifndef FDD
	while ((fc_flag = getopt(fc_argc, fc_argv, "B:c:d:D:Fkr:X:-:")) != -1)
#else
	while ((fc_flag = getopt(fc_argc, fc_argv, "B:c:d:D:M:Fkr:X:-:")) != -1)
#endif
	{
		switch (fc_flag)
		{
			case 'B':
				SetConfigOption("shared_buffers", optarg, PGC_POSTMASTER, PGC_S_ARGV);
				break;
			case 'D':
				fc_userDoption = pstrdup(optarg);
				break;
			case 'd':
				{
					/* 开启引导过程中调试。 */
					char	   *fc_debugstr;

					fc_debugstr = psprintf("debug%s", optarg);
					SetConfigOption("log_min_messages", fc_debugstr,
									PGC_POSTMASTER, PGC_S_ARGV);
					SetConfigOption("client_min_messages", fc_debugstr,
									PGC_POSTMASTER, PGC_S_ARGV);
					pfree(fc_debugstr);
				}
				break;
			case 'F':
				SetConfigOption("fsync", "false", PGC_POSTMASTER, PGC_S_ARGV);
				break;
			case 'k':
				bootstrap_data_checksum_version = PG_DATA_CHECKSUM_VERSION;
				break;
			case 'r':
				strlcpy(OutputFileName, optarg, MAXPGPATH);
				break;
#ifdef FDD
			case 'M':
				g_tdepass = pstrdup(optarg);
				break;
#endif
			case 'X':
				{
					int			fc_WalSegSz = strtoul(optarg, NULL, 0);

					if (!IsValidWalSegSize(fc_WalSegSz))
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
								 errmsg("-X requires a power of two value between 1 MB and 1 GB")));
					SetConfigOption("wal_segment_size", optarg, PGC_INTERNAL,
									PGC_S_DYNAMIC_DEFAULT);
				}
				break;
			case 'c':
			case '-':
				{
					char	   *fc_name,
							   *fc_value;

					ParseLongOption(optarg, &fc_name, &fc_value);
					if (!fc_value)
					{
						if (fc_flag == '-')
							ereport(ERROR,
									(errcode(ERRCODE_SYNTAX_ERROR),
									 errmsg("--%s requires a value",
											optarg)));
						else
							ereport(ERROR,
									(errcode(ERRCODE_SYNTAX_ERROR),
									 errmsg("-c %s requires a value",
											optarg)));
					}

					SetConfigOption(fc_name, fc_value, PGC_POSTMASTER, PGC_S_ARGV);
					free(fc_name);
					if (fc_value)
						free(fc_value);
					break;
				}
			default:
				write_stderr("Try \"%s --help\" for more information.\n",
							 fc_progname);
				proc_exit(1);
				break;
		}
	}

	if (fc_argc != optind)
	{
		write_stderr("%s: invalid command-line arguments\n", fc_progname);
		proc_exit(1);
	}
#ifdef FDD
	fdb_hideSensitiveArg("-M", 1);
#endif

	/* 获取配置参数 */
	if (!SelectConfigFiles(fc_userDoption, fc_progname))
		proc_exit(1);

	/*
	 * 验证我们是否被给定了一个看起来合理的 DataDir 并切换到它
	 */
	checkDataDir();
	ChangeToDataDir();

	CreateDataDirLockFile(false);

	SetProcessingMode(BootstrapProcessing);
	IgnoreSystemIndexes = true;

	InitializeMaxBackends();

	CreateSharedMemoryAndSemaphores();

	/*
	 * XXX：在某些时候将其移到自己的函数中可能是合理的。
	 * 现在看来，这样会导致代码重复多于其价值。
	 */
	if (fc_check_only)
	{
		SetProcessingMode(NormalProcessing);
		fc_CheckerModeMain();
		abort();
	}

	/*
	 * 为引导模式执行类似后端的初始化
	 */
	InitProcess();

	BaseInit();

	fc_bootstrap_signals();
	BootStrapXLOG();

	/*
	 * 为了确保 src/common/link-canary.c 被链接到后端，
	 * 我们必须从某个地方调用它。这里是个合适的地方。
	 */
	if (pg_link_canary_is_frontend())
		elog(ERROR, "backend is incorrectly linked to frontend functions");

	InitPostgres(NULL, InvalidOid, NULL, InvalidOid, false, false, NULL);

	/* 初始化引导文件处理的内容 */
	for (fc_i = 0; fc_i < MAXATTR; fc_i++)
	{
		attrtypes[fc_i] = NULL;
		Nulls[fc_i] = false;
	}

	/*
	 * 处理引导输入。
	 */
	StartTransactionCommand();
	boot_yyparse();
	CommitTransactionCommand();

	/*
	 * 我们现在应该了解所有映射的关系，因此可以写出
	 * 初始关系映射文件。
	 */
	RelationMapFinishBootstrap();

	/* 清理并退出 */
	fc_cleanup();
	proc_exit(0);
}


/* ----------------------------------------------------------------
 *						杂项函数
 * ----------------------------------------------------------------
 */

/*
 * 为引导过程设置信号处理
 */
static void fc_bootstrap_signals(void)
{
	Assert(!IsUnderPostmaster);

	/*
	 * 我们在引导模式下实际上不需要任何非默认信号处理；
	 * "蜷缩起来死去"是对所有这些情况的充分响应。
	 * 如果没有其他文档，咱们就显式地设置这种处理吧。
	 */
	pqsignal(SIGHUP, SIG_DFL);
	pqsignal(SIGINT, SIG_DFL);
	pqsignal(SIGTERM, SIG_DFL);
	pqsignal(SIGQUIT, SIG_DFL);
}

/* ----------------------------------------------------------------
 *				手动后端交互界面命令
 * ----------------------------------------------------------------
 */

/* ----------------
 *		boot_openrel
 *
 * 执行 BKI OPEN 命令。
 * ----------------
 */
void boot_openrel(char *fc_relname)
{
	int			fc_i;

	if (strlen(fc_relname) >= NAMEDATALEN)
		fc_relname[NAMEDATALEN - 1] = '\0';

	/*
	 * 在执行任何 OPEN 命令之前，pg_type 必须被填充，因此如果我们尚未填充，
	 * 现在可以填充 Typ。
	 */
	if (Typ == NIL)
		fc_populate_typ_list();

	if (boot_reldesc != NULL)
		closerel(NULL);

	elog(DEBUG4, "open relation %s, attrsize %d",
		 fc_relname, (int) ATTRIBUTE_FIXED_PART_SIZE);

	boot_reldesc = table_openrv(makeRangeVar(NULL, fc_relname, -1), NoLock);
	numattr = RelationGetNumberOfAttributes(boot_reldesc);
	for (fc_i = 0; fc_i < numattr; fc_i++)
	{
		if (attrtypes[fc_i] == NULL)
			attrtypes[fc_i] = fc_AllocateAttribute();
		memmove((char *) attrtypes[fc_i],
				(char *) TupleDescAttr(boot_reldesc->rd_att, fc_i),
				ATTRIBUTE_FIXED_PART_SIZE);

		{
			Form_pg_attribute fc_at = attrtypes[fc_i];

			elog(DEBUG4, "create attribute %d name %s len %d num %d type %u",
				 fc_i, NameStr(fc_at->attname), fc_at->attlen, fc_at->attnum,
				 fc_at->atttypid);
		}
	}
}

/* ----------------
 *		closerel
 * ----------------
 */
void closerel(char *fc_name)
{
	if (fc_name)
	{
		if (boot_reldesc)
		{
			if (strcmp(RelationGetRelationName(boot_reldesc), fc_name) != 0)
				elog(ERROR, "close of %s when %s was expected",
					 fc_name, RelationGetRelationName(boot_reldesc));
		}
		else
			elog(ERROR, "close of %s before any relation was opened",
				 fc_name);
	}

	if (boot_reldesc == NULL)
		elog(ERROR, "no open relation to close");
	else
	{
		elog(DEBUG4, "close relation %s",
			 RelationGetRelationName(boot_reldesc));
		table_close(boot_reldesc, NoLock);
		boot_reldesc = NULL;
	}
}



/* ----------------
 * DEFINEATTR()
 *
 * 定义 <field,type> 对
 * 如果要创建的关系中有 n 个字段，则此例程将被调用 n 次
 * ----------------
 */
void DefineAttr(char *fc_name, char *fc_type, int fc_attnum, int fc_nullness)
{
	Oid			fc_typeoid;

	if (boot_reldesc != NULL)
	{
		elog(WARNING, "no open relations allowed with CREATE command");
		closerel(NULL);
	}

	if (attrtypes[fc_attnum] == NULL)
		attrtypes[fc_attnum] = fc_AllocateAttribute();
	MemSet(attrtypes[fc_attnum], 0, ATTRIBUTE_FIXED_PART_SIZE);

	namestrcpy(&attrtypes[fc_attnum]->attname, fc_name);
	elog(DEBUG4, "column %s %s", NameStr(attrtypes[fc_attnum]->attname), fc_type);
	attrtypes[fc_attnum]->attnum = fc_attnum + 1;

	fc_typeoid = fc_gettype(fc_type);

	if (Typ != NIL)
	{
		attrtypes[fc_attnum]->atttypid = Ap->am_oid;
		attrtypes[fc_attnum]->attlen = Ap->am_typ.typlen;
		attrtypes[fc_attnum]->attbyval = Ap->am_typ.typbyval;
		attrtypes[fc_attnum]->attalign = Ap->am_typ.typalign;
		attrtypes[fc_attnum]->attstorage = Ap->am_typ.typstorage;
		attrtypes[fc_attnum]->attcompression = InvalidCompressionMethod;
		attrtypes[fc_attnum]->attcollation = Ap->am_typ.typcollation;
		/* 如果是数组类型，假设为一维属性 */
		if (Ap->am_typ.typelem != InvalidOid && Ap->am_typ.typlen < 0)
			attrtypes[fc_attnum]->attndims = 1;
		else
			attrtypes[fc_attnum]->attndims = 0;
	}
	else
	{
		attrtypes[fc_attnum]->atttypid = TypInfo[fc_typeoid].oid;
		attrtypes[fc_attnum]->attlen = TypInfo[fc_typeoid].len;
		attrtypes[fc_attnum]->attbyval = TypInfo[fc_typeoid].byval;
		attrtypes[fc_attnum]->attalign = TypInfo[fc_typeoid].align;
		attrtypes[fc_attnum]->attstorage = TypInfo[fc_typeoid].storage;
		attrtypes[fc_attnum]->attcompression = InvalidCompressionMethod;
		attrtypes[fc_attnum]->attcollation = TypInfo[fc_typeoid].collation;
		/* 如果是数组类型，假设为一维属性 */
		if (TypInfo[fc_typeoid].elem != InvalidOid &&
			attrtypes[fc_attnum]->attlen < 0)
			attrtypes[fc_attnum]->attndims = 1;
		else
			attrtypes[fc_attnum]->attndims = 0;
	}

	/*
	 * 如果系统目录列是区分排序的，强制其使用 C 排序，
	 * 以便其行为与数据库的排序无关。
	 * 这对允许使用不同数据库排序克隆 template0 是至关重要的。
	 */
	if (OidIsValid(attrtypes[fc_attnum]->attcollation))
		attrtypes[fc_attnum]->attcollation = C_COLLATION_OID;

	attrtypes[fc_attnum]->attstattarget = -1;
	attrtypes[fc_attnum]->attcacheoff = -1;
	attrtypes[fc_attnum]->atttypmod = -1;
	attrtypes[fc_attnum]->attislocal = true;

	if (fc_nullness == BOOTCOL_NULL_FORCE_NOT_NULL)
	{
		attrtypes[fc_attnum]->attnotnull = true;
	}
	else if (fc_nullness == BOOTCOL_NULL_FORCE_NULL)
	{
		attrtypes[fc_attnum]->attnotnull = false;
	}
	else
	{
		Assert(fc_nullness == BOOTCOL_NULL_AUTO);

		/*
		 * 如果类型是固定宽度且前面的列也是固定宽度且非空，则标记为“非空”。
		 * 这对应于通过 C 结构声明直接访问列的情况。
		 */
		if (attrtypes[fc_attnum]->attlen > 0)
		{
			int			fc_i;

			/* 检查早期属性 */
			for (fc_i = 0; fc_i < fc_attnum; fc_i++)
			{
				if (attrtypes[fc_i]->attlen <= 0 ||
					!attrtypes[fc_i]->attnotnull)
					break;
			}
			if (fc_i == fc_attnum)
				attrtypes[fc_attnum]->attnotnull = true;
		}
	}
}


/* ----------------
 *		InsertOneTuple
 *
 * 如果 objectid 不为零，则它是要分配给元组的特定 OID。
 * 否则，将由 heap_insert 分配 OID（如果需要）。
 * ----------------
 */
void InsertOneTuple(void)
{
	HeapTuple	fc_tuple;
	TupleDesc	fc_tupDesc;
	int			fc_i;

	elog(DEBUG4, "inserting row with %d columns", numattr);

	fc_tupDesc = CreateTupleDesc(numattr, attrtypes);
	fc_tuple = heap_form_tuple(fc_tupDesc, values, Nulls);
	pfree(fc_tupDesc);				/* 仅释放 tupDesc，而不是 attrtypes */

	simple_heap_insert(boot_reldesc, fc_tuple);
	heap_freetuple(fc_tuple);
	elog(DEBUG4, "row inserted");

	/*
	 * 重置下一个元组的空标记
	 */
	for (fc_i = 0; fc_i < numattr; fc_i++)
		Nulls[fc_i] = false;
}

/* ----------------
 *		插入单个值
 * ----------------
 */
void InsertOneValue(char *fc_value, int fc_i)
{
	Oid			fc_typoid;
	int16		fc_typlen;
	bool		fc_typbyval;
	char		fc_typalign;
	char		fc_typdelim;
	Oid			fc_typioparam;
	Oid			fc_typinput;
	Oid			fc_typoutput;

	AssertArg(fc_i >= 0 && fc_i < MAXATTR);

	elog(DEBUG4, "inserting column %d value \"%s\"", fc_i, fc_value);

	fc_typoid = TupleDescAttr(boot_reldesc->rd_att, fc_i)->atttypid;

	boot_get_type_io_data(fc_typoid,
						  &fc_typlen, &fc_typbyval, &fc_typalign,
						  &fc_typdelim, &fc_typioparam,
						  &fc_typinput, &fc_typoutput);

	values[fc_i] = OidInputFunctionCall(fc_typinput, fc_value, fc_typioparam, -1);

	/*
	 * 我们在这里使用ereport而不是elog，这样参数只有在消息将被打印时才会被评估，这通常不会发生
	 */
	ereport(DEBUG4,
			(errmsg_internal("inserted -> %s",
							 OidOutputFunctionCall(fc_typoutput, values[fc_i]))));
}

/* ----------------
 *		插入单个空值
 * ----------------
 */
void InsertOneNull(int fc_i)
{
	elog(DEBUG4, "inserting column %d NULL", fc_i);
	Assert(fc_i >= 0 && fc_i < MAXATTR);
	if (TupleDescAttr(boot_reldesc->rd_att, fc_i)->attnotnull)
		elog(ERROR,
			 "NULL value specified for not-null column \"%s\" of relation \"%s\"",
			 NameStr(TupleDescAttr(boot_reldesc->rd_att, fc_i)->attname),
			 RelationGetRelationName(boot_reldesc));
	values[fc_i] = PointerGetDatum(NULL);
	Nulls[fc_i] = true;
}

/* ----------------
 *		清理
 * ----------------
 */
static void fc_cleanup(void)
{
	if (boot_reldesc != NULL)
		closerel(NULL);
}

/* ----------------
 *		填充类型列表
 *
 * 通过读取pg_type加载类型列表。
 * ----------------
 */
static void fc_populate_typ_list(void)
{
	Relation	fc_rel;
	TableScanDesc fc_scan;
	HeapTuple	fc_tup;
	MemoryContext fc_old;

	Assert(Typ == NIL);

	fc_rel = table_open(TypeRelationId, NoLock);
	fc_scan = table_beginscan_catalog(fc_rel, 0, NULL);
	fc_old = MemoryContextSwitchTo(TopMemoryContext);
	while ((fc_tup = heap_getnext(fc_scan, ForwardScanDirection)) != NULL)
	{
		Form_pg_type fc_typForm = (Form_pg_type) GETSTRUCT(fc_tup);
		struct typmap *fc_newtyp;

		fc_newtyp = (struct typmap *) palloc(sizeof(struct typmap));
		Typ = lappend(Typ, fc_newtyp);

		fc_newtyp->am_oid = fc_typForm->oid;
		memcpy(&fc_newtyp->am_typ, fc_typForm, sizeof(fc_newtyp->am_typ));
	}
	MemoryContextSwitchTo(fc_old);
	table_endscan(fc_scan);
	table_close(fc_rel, NoLock);
}

/* ----------------
 *		获取类型
 *
 * 注意：这真的很丑陋；它会返回一个整数索引到TypInfo[]，而不是OID，
 * 直到第一次引用TypInfo[]中未知的类型。到那时，它将读取并缓存pg_type到Typ中，
 * 并随后返回一个真实的OID（并将全局指针Ap设置为指向Typ中找到的行）。因此调用者必须检查Typ是否
 * 仍然为NIL以确定返回值是什么！
 * ----------------
 */
static Oid fc_gettype(char *fc_type)
{
	if (Typ != NIL)
	{
		ListCell   *fc_lc;

		foreach(fc_lc, Typ)
		{
			struct typmap *fc_app = lfirst(fc_lc);

			if (strncmp(NameStr(fc_app->am_typ.typname), fc_type, NAMEDATALEN) == 0)
			{
				Ap = fc_app;
				return fc_app->am_oid;
			}
		}

		/*
		 * 类型未知；重新加载pg_type内容并再次检查
		 * 以处理自上次填充列表以来添加的复合类型。
		 */

		list_free_deep(Typ);
		Typ = NIL;
		fc_populate_typ_list();

		/*
		 * 调用gettype将导致无限递归，对于缺失于pg_type的类型，
		 * 所以只是重复查找。
		 */
		foreach(fc_lc, Typ)
		{
			struct typmap *fc_app = lfirst(fc_lc);

			if (strncmp(NameStr(fc_app->am_typ.typname), fc_type, NAMEDATALEN) == 0)
			{
				Ap = fc_app;
				return fc_app->am_oid;
			}
		}
	}
	else
	{
		int			fc_i;

		for (fc_i = 0; fc_i < n_types; fc_i++)
		{
			if (strncmp(fc_type, TypInfo[fc_i].name, NAMEDATALEN) == 0)
				return fc_i;
		}
		/* 不在TypInfo中，所以我们现在最好能读取pg_type */
		elog(DEBUG4, "external type: %s", fc_type);
		fc_populate_typ_list();
		return fc_gettype(fc_type);
	}
	elog(ERROR, "unrecognized type \"%s\"", fc_type);
	/* 未达到，此处使编译器满意 */
	return 0;
}

/* ----------------
 *		引导获取类型I/O数据
 *
 * 在引导时获取类型I/O信息。 这故意有
 * 与lsyscache.c的get_type_io_data几乎相同的API，除了我们仅支持获取typinput和typoutput例程，
 * 不支持二进制I/O例程。它被导出，以便array_in和array_out
 * 可以在早期引导期间工作。
 * ----------------
 */
void boot_get_type_io_data(Oid fc_typid,
					  int16 *fc_typlen,
					  bool *fc_typbyval,
					  char *fc_typalign,
					  char *fc_typdelim,
					  Oid *fc_typioparam,
					  Oid *fc_typinput,
					  Oid *fc_typoutput)
{
	if (Typ != NIL)
	{
		/* 我们有pg_type的引导时内容，所以使用它 */
		struct typmap *fc_ap = NULL;
		ListCell   *fc_lc;

		foreach(fc_lc, Typ)
		{
			fc_ap = lfirst(fc_lc);
			if (fc_ap->am_oid == fc_typid)
				break;
		}

		if (!fc_ap || fc_ap->am_oid != fc_typid)
			elog(ERROR, "type OID %u not found in Typ list", fc_typid);

		*fc_typlen = fc_ap->am_typ.typlen;
		*fc_typbyval = fc_ap->am_typ.typbyval;
		*fc_typalign = fc_ap->am_typ.typalign;
		*fc_typdelim = fc_ap->am_typ.typdelim;

		/* XXX 这个逻辑必须与getTypeIOParam()匹配 */
		if (OidIsValid(fc_ap->am_typ.typelem))
			*fc_typioparam = fc_ap->am_typ.typelem;
		else
			*fc_typioparam = fc_typid;

		*fc_typinput = fc_ap->am_typ.typinput;
		*fc_typoutput = fc_ap->am_typ.typoutput;
	}
	else
	{
		/* 我们还没有pg_type，所以使用硬编码的TypInfo数组 */
		int			fc_typeindex;

		for (fc_typeindex = 0; fc_typeindex < n_types; fc_typeindex++)
		{
			if (TypInfo[fc_typeindex].oid == fc_typid)
				break;
		}
		if (fc_typeindex >= n_types)
			elog(ERROR, "type OID %u not found in TypInfo", fc_typid);

		*fc_typlen = TypInfo[fc_typeindex].len;
		*fc_typbyval = TypInfo[fc_typeindex].byval;
		*fc_typalign = TypInfo[fc_typeindex].align;
		/* 我们假设引导时的typdelim为',' */
		*fc_typdelim = ',';

		/* XXX 这个逻辑必须与getTypeIOParam()匹配 */
		if (OidIsValid(TypInfo[fc_typeindex].elem))
			*fc_typioparam = TypInfo[fc_typeindex].elem;
		else
			*fc_typioparam = fc_typid;

		*fc_typinput = TypInfo[fc_typeindex].inproc;
		*fc_typoutput = TypInfo[fc_typeindex].outproc;
	}
}

/* ----------------
 *		分配属性
 *
 * 注意：引导期间从不设置任何每列ACL，因此每个属性只需要
 * ATTRIBUTE_FIXED_PART_SIZE的空间。
 * ----------------
 */
static Form_pg_attribute
fc_AllocateAttribute(void)
{
	return (Form_pg_attribute)
		MemoryContextAllocZero(TopMemoryContext, ATTRIBUTE_FIXED_PART_SIZE);
}

/*
 *	index_register() -- 记录已设置以供稍后构建的索引
 *
 *		在引导时，我们在系统目录上定义了一堆索引。
 *		然而，我们将实际构建索引的时间推迟到我们完成
 *		初始化之前。 这是因为索引
 *		本身有目录条目，而这些必须包含在
 *		这些目录的索引中。分两步进行是确保索引最终具有
 *		正确内容的最简单方法。
 */
void index_register(Oid fc_heap,
			   Oid fc_ind,
			   IndexInfo *fc_indexInfo)
{
	IndexList  *fc_newind;
	MemoryContext fc_oldcxt;

	/*
	 * XXX mao 10/31/92 -- 在引导期间不要gc索引reldescs，相关信息。
	 * 我们现在会声明索引，但想在以后创建它们。
	 */

	if (nogc == NULL)
		nogc = AllocSetContextCreate(NULL,
									 "BootstrapNoGC",
									 ALLOCSET_DEFAULT_SIZES);

	fc_oldcxt = MemoryContextSwitchTo(nogc);

	fc_newind = (IndexList *) palloc(sizeof(IndexList));
	fc_newind->il_heap = fc_heap;
	fc_newind->il_ind = fc_ind;
	fc_newind->il_info = (IndexInfo *) palloc(sizeof(IndexInfo));

	memcpy(fc_newind->il_info, fc_indexInfo, sizeof(IndexInfo));
	/* 表达式可能为空，但也可以复制它 */
	fc_newind->il_info->ii_Expressions =
		copyObject(fc_indexInfo->ii_Expressions);
	fc_newind->il_info->ii_ExpressionsState = NIL;
	/* 谓词可能为空，但也可以复制它 */
	fc_newind->il_info->ii_Predicate =
		copyObject(fc_indexInfo->ii_Predicate);
	fc_newind->il_info->ii_PredicateState = NULL;
	/* 在引导期间没有排除约束，因此不需要复制 */
	Assert(fc_indexInfo->ii_ExclusionOps == NULL);
	Assert(fc_indexInfo->ii_ExclusionProcs == NULL);
	Assert(fc_indexInfo->ii_ExclusionStrats == NULL);

	fc_newind->il_next = ILHead;
	ILHead = fc_newind;

	MemoryContextSwitchTo(fc_oldcxt);
}


/*
 * build_indices -- 填写所有之前注册的索引
 */
void build_indices(void)
{
	for (; ILHead != NULL; ILHead = ILHead->il_next)
	{
		Relation	fc_heap;
		Relation	fc_ind;

		/* 在引导期间无需担心锁 */
		fc_heap = table_open(ILHead->il_heap, NoLock);
		fc_ind = index_open(ILHead->il_ind, NoLock);

		index_build(fc_heap, fc_ind, ILHead->il_info, false, false);

		index_close(fc_ind, NoLock);
		table_close(fc_heap, NoLock);
	}
}
